package top.chukongxiang.project.base.oauth2.provider.cas;

import lombok.Setter;
import lombok.experimental.Accessors;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import top.chukongxiang.project.base.oauth2.UserInfo;

import java.util.Map;

/**
 * Oauth2-Cas自定义授权方式
 * @author 楚孔响
 * @version 1.0
 * @date 2021/12/31 9:32
 */
public class CasTokenGranter extends AbstractTokenGranter {

    private ServiceProperties serviceProperties = null;
    private AuthenticationManager authenticationManager = null;

    /**
     * grant_type=cas
     */
    public static final String GRANT_TYPE_PARAMETER_NAME = "grant_type";
    public static final String GRANT_TYPE = "cas";

    private CasTokenGranter(AuthenticationManager authenticationManager,
                           ServiceProperties serviceProperties,
                           AuthorizationServerTokenServices tokenServices,
                           ClientDetailsService clientDetailsService,
                           OAuth2RequestFactory requestFactory) {
        this(tokenServices, clientDetailsService, requestFactory, null);
        this.authenticationManager = authenticationManager;
        this.serviceProperties = serviceProperties;
    }

    private CasTokenGranter(AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {
        super(tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);
    }

    @Override
    public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
        OAuth2AccessToken token = super.grant(grantType, tokenRequest);
        if (token != null) {
            DefaultOAuth2AccessToken norefresh = new DefaultOAuth2AccessToken(token);
            // cas认证不可刷新token
            norefresh.setRefreshToken(null);
            token = norefresh;
        }
        return token;
    }

    /**
     * cas仅支持集成用户名为
     * {@link CasAuthenticationFilter#CAS_STATEFUL_IDENTIFIER} 或 {@link CasAuthenticationFilter#CAS_STATELESS_IDENTIFIER}
     * 的 {@link UsernamePasswordAuthenticationToken}，不支持其他类型的认证。
     *
     * BUG：(已解决)
     * 因该项目前端默认使用password模式，后台需要把{@link CasAuthenticationFilter#CAS_STATEFUL_IDENTIFIER} 和 {@link CasAuthenticationFilter#CAS_STATELESS_IDENTIFIER}这两个用户禁用掉或写死不可注册
     * 否则一旦用户名为以上两个值，即使grant_type不为cas，也会使用cas的provider进行授权认证！！！
     * @param client
     * @param tokenRequest
     * @return
     */
    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

        Map<String, String> parameters = tokenRequest.getRequestParameters();
        String username = CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER;
        String password = parameters.get(serviceProperties.getArtifactParameter());
        if (password == null) {
            throw new IllegalArgumentException("ticket不能为空");
        }

        String service = parameters.get(serviceProperties.getServiceParameter());
        if(service!=null && !service.equals(serviceProperties.getService())) {
            // TODO
            //  此处有安全问题，恶意修改service地址将导致整个项目的cas service验证地址全部被修改
            //  学校是否使用webvpn？如果不使用，推荐固定写死（删除此部分，在Provider内修改默认地址）
            //  如果使用webvpn，则需要保留，可以进行RSA加密，但用户体验会差
            serviceProperties.setService(service);
        }
//        TODO 如使用webvpn 反射获取serviceProperties的prefix进行修改，安全问题同上
//        String prefix = parameters.get("prefix");
//        if (!serviceProperties.)

        // 创建用户Principal信息，注意：因需要集成授权，此步骤仅用作获取用户授权的身份构建UsernamePasswordAuthenticationToken，不可用作其他用途
        UserDetails userDetails = new UserInfo(username, password, null);
        // 构建 Oauth2Request
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(client);
        // 构建认证基础信息
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                new UsernamePasswordAuthenticationToken(username, password, userDetails.getAuthorities());
//        usernamePasswordAuthenticationToken.setAuthenticated(false);
//        usernamePasswordAuthenticationToken.setDetails(tokenRequest.getRequestParameters());
        Authentication authenticate;
        try {
            // 手动进行认证 CasAuthenticationProvider
            authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
            Object principal = authenticate.getPrincipal();
            logger.debug(principal.toString() + "认证成功");
        } catch (Exception e) {
            logger.error("", e);
            throw new InvalidGrantException(e.getMessage());
        }
        if (!usernamePasswordAuthenticationToken.isAuthenticated()) {
            // 授权不成功，如果本服务器或cas服务器网络环境较差可能会出现此情况
            throw new InvalidGrantException("无法验证ticket: " + password);
        }
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authenticate);
        oAuth2Authentication.setDetails(parameters);
        // 是否已认证，默认false
        oAuth2Authentication.setAuthenticated(true);
        return oAuth2Authentication;
    }

    /**
     * 去除反射，使用工厂模式构建
     */
    @Setter
    @Accessors(chain = true, fluent = true)
    public static class Builder {
        private ServiceProperties serviceProperties;
        private AuthenticationManager authenticationManager;
        private AuthorizationServerTokenServices authorizationServerTokenServices;
        private ClientDetailsService clientDetailsService;
        private OAuth2RequestFactory oAuth2RequestFactory;

        public CasTokenGranter build() {
            return new CasTokenGranter(authenticationManager, serviceProperties, authorizationServerTokenServices, clientDetailsService, oAuth2RequestFactory);
        }
    }

    public static Builder builder() {
        return new Builder();
    }
}
